/*
 * UTILS.C
 *
 * Colorful, sorted and optionally rich directory enumeration
 * for Windows.
 *
 * This module contains helper functions that can be used in a variety of
 * situations.
 *
 * Copyright (c) 2014-2016 Malcolm J. Smith
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "sdir.h"

//
//  Sorting support
//

DWORD
SdirCompareLargeInt (
    __in PULARGE_INTEGER Left,
    __in PULARGE_INTEGER Right
    )
{
    if (Left->HighPart < Right->HighPart) {
        return SDIR_LESS_THAN;
    } else if (Left->HighPart > Right->HighPart) {
        return SDIR_GREATER_THAN;
    }

    if (Left->LowPart < Right->LowPart) {
        return SDIR_LESS_THAN;
    } else if (Left->LowPart > Right->LowPart) {
        return SDIR_GREATER_THAN;
    }

    return SDIR_EQUAL;
}

DWORD
SdirCompareString (
    __in LPCTSTR Left,
    __in LPCTSTR Right
    )
{
    int ret = _tcsicmp(Left, Right);

    if (ret < 0) {
        return SDIR_LESS_THAN;
    } else if (ret == 0) {
        return SDIR_EQUAL;
    } else {
        return SDIR_GREATER_THAN;
    }
}

DWORD
SdirCompareDate (
    __in LPSYSTEMTIME Left,
    __in LPSYSTEMTIME Right
    )
{
    if (Left->wYear < Right->wYear) {
        return SDIR_LESS_THAN;
    } else if (Left->wYear > Right->wYear) {
        return SDIR_GREATER_THAN;
    }

    if (Left->wMonth < Right->wMonth) {
        return SDIR_LESS_THAN;
    } else if (Left->wMonth > Right->wMonth) {
        return SDIR_GREATER_THAN;
    }

    if (Left->wDay < Right->wDay) {
        return SDIR_LESS_THAN;
    } else if (Left->wDay > Right->wDay) {
        return SDIR_GREATER_THAN;
    }

    return SDIR_EQUAL;
}

DWORD
SdirCompareTime (
    __in LPSYSTEMTIME Left,
    __in LPSYSTEMTIME Right
    )
{
    if (Left->wHour < Right->wHour) {
        return SDIR_LESS_THAN;
    } else if (Left->wHour > Right->wHour) {
        return SDIR_GREATER_THAN;
    }

    if (Left->wMinute < Right->wMinute) {
        return SDIR_LESS_THAN;
    } else if (Left->wMinute > Right->wMinute) {
        return SDIR_GREATER_THAN;
    }

    if (Left->wSecond < Right->wSecond) {
        return SDIR_LESS_THAN;
    } else if (Left->wSecond > Right->wSecond) {
        return SDIR_GREATER_THAN;
    }

    if (Left->wMilliseconds < Right->wMilliseconds) {
        return SDIR_LESS_THAN;
    } else if (Left->wMilliseconds > Right->wMilliseconds) {
        return SDIR_GREATER_THAN;
    }

    return SDIR_EQUAL;
}

//
//  These are general helper routines to assist in converting strings into
//  something we can use directly.
//

DWORD
SdirStringToNum32(
    __in LPCTSTR str,
    __out_opt LPCTSTR* endptr
    )
{
    DWORD ret = 0;
    if (str[0] == '0' && str[1] == 'x') {
        str += 2;
        while ((*str >= '0' && *str <= '9') || (*str >= 'a' && *str <= 'f') || (*str >= 'A' && *str <= 'F')) {
            ret *= 16;
            if (*str >= '0' && *str <= '9') {
                ret += *str - '0';
            } else if (*str >= 'a' && *str <= 'f') {
                ret += *str - 'a' + 10;
            } else if (*str >= 'A' && *str <= 'F') {
                ret += *str - 'A' + 10;
            }
            str++;
        }
    } else {
        while (*str >= '0' && *str <= '9') {
            ret *= 10;
            ret += *str - '0';
            str++;
        }
    }
    if (endptr) {
        *endptr = str;
    }
    return ret;
}

LARGE_INTEGER
SdirStringToNum64(
    __in LPCTSTR str,
    __out_opt LPCTSTR* endptr
    )
{
    LARGE_INTEGER ret = {0};
    if (str[0] == '0' && str[1] == 'x') {
        str += 2;
    }
    while ((*str >= '0' && *str <= '9') || (*str >= 'a' && *str <= 'f') || (*str >= 'A' && *str <= 'F')) {
        ret.HighPart = (ret.HighPart<<4) + ((ret.LowPart & 0xf0000000)>>28);
        ret.LowPart *= 16;
        if (*str >= '0' && *str <= '9') {
            ret.LowPart += *str - '0';
        } else if (*str >= 'a' && *str <= 'f') {
            ret.LowPart += *str - 'a' + 10;
        } else if (*str >= 'A' && *str <= 'F') {
            ret.LowPart += *str - 'A' + 10;
        }
        str++;
    }
    if (endptr) {
        *endptr = str;
    }
    return ret;
}

BOOL
SdirStringToHexBuffer(
    __in LPCTSTR str,
    __out PUCHAR buffer,
    __in DWORD size,
    __out_opt LPCTSTR* endptr
    )
{
    UCHAR thischar;
    DWORD offset;
    DWORD digit;

    //
    //  Loop through the output string
    //

    for (offset = 0; offset < size; offset++) {

        thischar = 0;
        digit = 0;

        //
        //  So long as we have a valid character, populate thischar.  Do this exactly
        //  twice, so we have one complete byte.
        //

        while ((digit < 2) &&
               ((*str >= '0' && *str <= '9') || (*str >= 'a' && *str <= 'f') || (*str >= 'A' && *str <= 'F'))) {
            thischar *= 16;
            if (*str >= '0' && *str <= '9') {
                thischar += *str - '0';
            } else if (*str >= 'a' && *str <= 'f') {
                thischar += *str - 'a' + 10;
            } else if (*str >= 'A' && *str <= 'F') {
                thischar += *str - 'A' + 10;
            }
            str++;
            digit++;
        }

        //
        //  If we did actually get two input chars, output the byte and go back for
        //  more.  Otherwise, the input doesn't match the output requirement
        //

        if (digit == 2) {
            buffer[offset] = thischar;
        } else {
            return FALSE;
        }
    }
    return TRUE;
}

LARGE_INTEGER
SdirStringToFileSize(
    __in LPCTSTR str
    )
{
    TCHAR Suffixes[] = {'b', 'k', 'm', 'g', 't'};
    DWORD SuffixLevel = 0;
    LARGE_INTEGER FileSize;
    DWORD i;
    TCHAR * endptr;

    FileSize.HighPart = 0;
    FileSize.LowPart = SdirStringToNum32(str, &endptr);

    for (i = 0; i < sizeof(Suffixes)/sizeof(Suffixes[0]); i++) {
        if (*endptr == Suffixes[i] || *endptr == (Suffixes[i] + 'A' - 'a')) {
            SuffixLevel = i;
            break;
        }
    }

    while(SuffixLevel) {

        FileSize.HighPart = (FileSize.HighPart << 10) + (FileSize.LowPart >> 22);
        FileSize.LowPart = (FileSize.LowPart << 10);

        SuffixLevel--;
    }

    return FileSize;
}

BOOL
SdirStringToDate(
    __in LPCTSTR str,
    __out LPSYSTEMTIME date
    )
{
    TCHAR * endptr = NULL;
    date->wYear = (WORD)SdirStringToNum32(str, &endptr);
    if (date->wYear < 100) {
        date->wYear += 2000;
    }
    if (*endptr == '/') {
        date->wMonth = (WORD)SdirStringToNum32(endptr + 1, &endptr);
        if (*endptr == '/') {
            date->wDay = (WORD)SdirStringToNum32(endptr + 1, &endptr);
        }
    }

    return TRUE;
}

BOOL
SdirStringToTime(
    __in LPCTSTR str,
    __out LPSYSTEMTIME date
    )
{
    TCHAR * endptr = NULL;
    date->wHour = (WORD)SdirStringToNum32(str, &endptr);
    if (*endptr == ':') {
        date->wMinute = (WORD)SdirStringToNum32(endptr + 1, &endptr);
        if (*endptr == ':') {
            date->wSecond = (WORD)SdirStringToNum32(endptr + 1, &endptr);
        }
    }

    return TRUE;
}

//
//  Formatting support.  Generic routines used across different types of data.
//

ULONG
SdirDisplayGenericSize(
    __out_ecount(6) PSDIR_FMTCHAR Buffer,
    __in WORD Attributes,
    __in PLARGE_INTEGER GenericSize
    )
{
    TCHAR Suffixes[] = {'b', 'k', 'm', 'g', 't', '?'};
    DWORD SuffixLevel = 0;
    TCHAR StrSize[10];
        
    if (Buffer) {
        LARGE_INTEGER Size = *GenericSize;
        LARGE_INTEGER OldSize = Size;

        while (Size.HighPart != 0 || Size.LowPart > 9999) {
            SuffixLevel++;
            OldSize = Size;

            //
            //  Conceptually we want to divide by 1024.  We do this
            //  in two 32-bit shifts combining back the high bits
            //  so we don't need full compiler support.
            //

            Size.LowPart = (Size.LowPart >> 10) + ((Size.HighPart & 0x3ff) << 22);
            Size.HighPart = Size.HighPart >> 10;
        }

        if (SuffixLevel >= sizeof(Suffixes)/sizeof(TCHAR)) {
            SuffixLevel = sizeof(Suffixes)/sizeof(TCHAR) - 1;
        }
            
        if (Size.LowPart < 100 && SuffixLevel > 0) {
            OldSize.LowPart = (OldSize.LowPart % 1024) * 10 / 1024;
            _stprintf_s(StrSize,
                        sizeof(StrSize)/sizeof(StrSize[0]),
                        _T(" %2i.%1i%c"),
                        Size.LowPart,
                        OldSize.LowPart,
                        Suffixes[SuffixLevel]);
        } else {
            _stprintf_s(StrSize,
                        sizeof(StrSize)/sizeof(StrSize[0]),
                        _T(" %4i%c"),
                        Size.LowPart,
                        Suffixes[SuffixLevel]);
        }
            
        SdirPasteStr(Buffer, StrSize, Attributes, 6);
    }
    return 6;
}

ULONG
SdirDisplayHex64 (
    __out_ecount(18) PSDIR_FMTCHAR Buffer,
    __in WORD Attributes,
    __in PLARGE_INTEGER Hex
    )
{
    TCHAR Str[19];

    if (Buffer) {
        _stprintf_s(Str, sizeof(Str)/sizeof(Str[0]), _T(" %08x`%08x"), Hex->HighPart, Hex->LowPart);
        SdirPasteStr(Buffer, Str, Attributes, 18);
    }

    return 18;
}

ULONG
SdirDisplayHex32 (
    __out_ecount(9) PSDIR_FMTCHAR Buffer,
    __in WORD Attributes,
    __in DWORD Hex
    )
{
    TCHAR Str[10];

    if (Buffer) {
        _stprintf_s(Str, sizeof(Str)/sizeof(Str[0]), _T(" %08x"), Hex);
        SdirPasteStr(Buffer, Str, Attributes, 9);
    }
    return 9;
}

ULONG
SdirDisplayGenericHexBuffer (
    __out_ecount(Size * 2 + 1) PSDIR_FMTCHAR Buffer,
    __in WORD Attributes,
    __in PUCHAR InputBuffer,
    __in DWORD Size
    )
{
    TCHAR Str[3];
    DWORD Offset;

    if (Buffer) {
        SdirPasteStr(Buffer, _T(" "), Attributes, 1);
        for (Offset = 0; Offset < Size; Offset++) {
            _stprintf_s(Str, sizeof(Str)/sizeof(Str[0]), _T("%02x"), InputBuffer[Offset]);
            SdirPasteStr(Buffer + 1 + 2 * Offset, Str, Attributes, 2);
        }
    }

    return Size * 2 + 1;
}

ULONG
SdirDisplayFileDate (
    __out_ecount(11) PSDIR_FMTCHAR Buffer,
    __in WORD Attributes,
    __in SYSTEMTIME * Time
    )
{
    TCHAR StrDate[16];

    if (Buffer) {
        _stprintf_s(StrDate,
                    sizeof(StrDate)/sizeof(StrDate[0]),
                    _T(" %04i/%02i/%02i"),
                    Time->wYear,
                    Time->wMonth,
                    Time->wDay);

        SdirPasteStr(Buffer, StrDate, Attributes, 11);
    }
    return 11;
}

ULONG
SdirDisplayFileTime (
    __out_ecount(9) PSDIR_FMTCHAR Buffer,
    __in WORD Attributes,
    __in SYSTEMTIME * Time
    )
{
    TCHAR StrTime[10];

    if (Buffer) {
        _stprintf_s(StrTime,
                    sizeof(StrTime)/sizeof(StrTime[0]),
                    _T(" %02i:%02i:%02i"),
                    Time->wHour,
                    Time->wMinute,
                    Time->wSecond);

        SdirPasteStr(Buffer, StrTime, Attributes, 9);
    }
    return 9;
}

BOOL
SdirIsFullPathUnc(
    __in LPCTSTR szPath
    )
{
    //
    //  This function assumes it's dealing with paths already prepended
    //  with \\?\ and does not attempt to detect other forms of UNC paths.
    //

    if ((szPath[4] == 'U' || szPath[4] == 'u') &&
        (szPath[5] == 'N' || szPath[5] == 'n') &&
        (szPath[6] == 'C' || szPath[6] == 'c') &&
        (szPath[7] == '\\')) {

        return TRUE;
    }
    return FALSE;
}

#define SdirIsSep(x) ((x) == '\\' || (x) == '/')

BOOL
SdirGetFullPathNameReturnAllocation(
    __in LPCTSTR szFileName,
    __out LPTSTR* lpBuffer,
    __out_opt LPTSTR* lpFilePart
    )
{
#if !defined(SDIR_SUPPORT_LONGPATH) || !defined(UNICODE)
    DWORD dwSize;
    DWORD dwNewSize;
    LPTSTR AbsoluteName;
    LPTSTR DummyFilePart;

    dwSize = GetFullPathName(szFileName, 0, NULL, NULL);
    if (dwSize == 0) {
        return FALSE;
    }

    //
    //  Hack for old versions that don't account for NULL correctly
    //

    dwSize++;

    AbsoluteName = HeapAlloc(GetProcessHeap(), 0, dwSize * sizeof(TCHAR));
    if (AbsoluteName == NULL) {
        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        return FALSE;
    }

    dwNewSize = GetFullPathName(szFileName, dwSize, AbsoluteName, &DummyFilePart);
    if (dwNewSize == 0 || dwNewSize > dwSize) {
        HeapFree(GetProcessHeap(), 0, AbsoluteName);
        return FALSE;
    }

    *lpBuffer = AbsoluteName;
    if (lpFilePart != NULL) {
        *lpFilePart = DummyFilePart;
    }

    return TRUE;
#else
    //
    //  This function is a drop in replacement for GetFullPathName.  It
    //  returns paths prepended with \\?\, which is advantageous for a
    //  few reasons:
    //  1. "C:\con" (et al) is treated like a legitimate file, not a
    //     console.
    //  2. "C:\file " (trailing space) is preserved without truncation.
    //
    //  Cases this function needs to handle:
    //  1. Absolute paths, starting with \\ or X:\.  We need to remove
    //     relative components from these.
    //  2. Drive relative paths (C:foo).  Here we need to merge with the
    //     current directory for the specified drive.
    //  3. Absolute paths without drive (\foo).  Here we need to merge
    //     with the current drive letter.
    //  4. Relative paths, with no prefix.  These need to have current
    //     directory prepended.
    //
    //  Regardless of the case above, this function should flatten out
    //  any "." or ".." components in the path.
    //

    BOOLEAN DriveRelativePath = FALSE;
    BOOLEAN AbsoluteWithoutDrive = FALSE;
    BOOLEAN RelativePath = FALSE;
    BOOLEAN PrefixPresent = FALSE;
    BOOLEAN UncPath = FALSE;
    BOOLEAN PreviousWasSeperator;

    LPTSTR EffectiveRoot;
    LPCTSTR StartOfRelativePath = szFileName;

    LPTSTR AbsoluteName = NULL;
    LPTSTR CurrentReadChar;
    LPTSTR CurrentWriteChar;

    DWORD Result;

    //
    //  First, determine which of the cases above we're processing.
    //

    if (SdirIsSep(szFileName[0]) &&
        SdirIsSep(szFileName[1])) {

        UncPath = TRUE;

        if ((szFileName[2] == '?' ||
             szFileName[2] == '.' ) &&
            SdirIsSep(szFileName[3])) {

            PrefixPresent = TRUE;
        }

    } else if (((szFileName[0] >= 'a' && szFileName[0] <= 'z') ||
                (szFileName[0] >= 'A' && szFileName[0] <= 'Z')) &&
               (szFileName[1] == ':')) {

        if (!SdirIsSep(szFileName[2])) {
            DriveRelativePath = TRUE;
            StartOfRelativePath = &szFileName[2];
        }
    } else if (SdirIsSep(szFileName[0])) {
        AbsoluteWithoutDrive = TRUE;
    } else {
        RelativePath = TRUE;
    }

    //
    //  If it's a relative case, get the current directory, and generate an
    //  "absolute" form of the name.  If it's an absolute case, prepend
    //  \\?\ and have a buffer we allocate for subsequent munging.
    //

    if (DriveRelativePath || RelativePath || AbsoluteWithoutDrive) {

        LPTSTR CurrentDirectory = NULL;
        DWORD CurrentDirectoryLength = 0;

        //
        //  If it's drive relative, get the current directory of the requested
        //  drive.  The only documented way to do this is to call
        //  GetFullPathName itself, which looks a little goofy given where
        //  we are.
        //

        if (DriveRelativePath) {
            TCHAR szDrive[3];

            LPTSTR FinalPart;
            szDrive[0] = szFileName[0];
            szDrive[1] = szFileName[1];
            szDrive[2] = '\0';

            //
            //  Add space for a NULL, which is really a workaround for old OS'es.
            //

            CurrentDirectoryLength = GetFullPathName(szDrive, 0, NULL, &FinalPart);
            CurrentDirectoryLength ++;
            CurrentDirectory = HeapAlloc(GetProcessHeap(), 0, CurrentDirectoryLength * sizeof(TCHAR));

            if (CurrentDirectory == NULL) {
                SetLastError(ERROR_NOT_ENOUGH_MEMORY);
                return FALSE;
            }

            Result = GetFullPathName(szDrive, CurrentDirectoryLength, CurrentDirectory, &FinalPart);
            if (Result == 0 || Result >= CurrentDirectoryLength) {
                HeapFree(GetProcessHeap(), 0, CurrentDirectory);
                return FALSE;
            }
        } else {
            CurrentDirectoryLength = GetCurrentDirectory(0, NULL);
            CurrentDirectory = HeapAlloc(GetProcessHeap(), 0, CurrentDirectoryLength * sizeof(TCHAR));
    
            if (CurrentDirectory == NULL) {
                SetLastError(ERROR_NOT_ENOUGH_MEMORY);
                return FALSE;
            }
    
            Result = GetCurrentDirectory(CurrentDirectoryLength, CurrentDirectory);
            if (Result == 0 || Result >= CurrentDirectoryLength) {
                HeapFree(GetProcessHeap(), 0, CurrentDirectory);
                return FALSE;
            }
        }

        //
        //  Just truncate the current directory two two chars and let
        //  normal processing continue.
        //

        if (AbsoluteWithoutDrive) {
            if (CurrentDirectoryLength > 2) {
                CurrentDirectoryLength = 3;
                CurrentDirectory[2] = '\0';
            }
        }

        //
        //  If current directory is UNC, need to convert to
        //  \\?\unc\...
        //

        if (SdirIsSep(CurrentDirectory[0]) &&
            SdirIsSep(CurrentDirectory[1])) {

            UncPath = TRUE;
        }

        //
        //  Calculate the length of the "absolute" form of this path and
        //  generate it through concatenation.  This is the prefix, the
        //  current directory, a seperator, and the relative path.  Note
        //  the current directory length includes space for a NULL.
        //

        Result = 4 + CurrentDirectoryLength + (DWORD)_tcslen(StartOfRelativePath) + 1;
        if (UncPath) {
            Result += 3; // Remove "\\" and add "\UNC\"
        }

        AbsoluteName = HeapAlloc(GetProcessHeap(), 0, Result * sizeof(TCHAR));
        if (AbsoluteName == NULL) {
            HeapFree(GetProcessHeap(), 0, CurrentDirectory);
            SetLastError(ERROR_NOT_ENOUGH_MEMORY);
            return FALSE;
        }

        if (UncPath) {
            _stprintf_s(AbsoluteName, Result, _T("\\\\%c\\UNC\\%s\\%s"), Opts->PathPrefixChar, &CurrentDirectory[2], StartOfRelativePath);
        } else {
            _stprintf_s(AbsoluteName, Result, _T("\\\\%c\\%s\\%s"), Opts->PathPrefixChar, CurrentDirectory, StartOfRelativePath);
        }
        HeapFree(GetProcessHeap(), 0, CurrentDirectory);
    } else {
        DWORD Length;

        //
        //  Allocate a buffer for the "absolute" path so we can do compaction.
        //  If the path is UNC, convert to \\?\UNC\...
        //

        Length = (DWORD)_tcslen(szFileName) + 1 + 4;
        if (!PrefixPresent && UncPath) {
            Length += 4;
        }

        AbsoluteName = HeapAlloc(GetProcessHeap(), 0, Length * sizeof(TCHAR));
        if (AbsoluteName == NULL) {
            SetLastError(ERROR_NOT_ENOUGH_MEMORY);
            return FALSE;
        }

        if (PrefixPresent) {
            _stprintf_s(AbsoluteName, Length, _T("%s"), szFileName);
        } else if (UncPath) {
            _stprintf_s(AbsoluteName, Length, _T("\\\\%c\\UNC\\%s"), Opts->PathPrefixChar, &szFileName[2]);
        } else {
            _stprintf_s(AbsoluteName, Length, _T("\\\\%c\\%s"), Opts->PathPrefixChar, szFileName);
        }
    }

    //
    //  Convert forward slashes to backslashes across the entire path
    //

    for (CurrentReadChar = AbsoluteName; *CurrentReadChar != '\0'; CurrentReadChar++) {

        if (*CurrentReadChar == '/') {
            *CurrentReadChar = '\\';
        }
    }

    //
    //  At this point we should have an allocated, "absolute" name.  We still
    //  need to process relative components, and as we do so, there's a point
    //  we can't traverse back through.  Eg. we don't do relative paths
    //  before \\?\X:\ or \\?\UNC\server\share.
    //

    if (PrefixPresent) {
        if (SdirIsFullPathUnc(AbsoluteName)) {
            UncPath = TRUE;
        } else {
            UncPath = FALSE;
        }
    }

    if (UncPath) {

        EffectiveRoot = _tcschr(&AbsoluteName[8], '\\');

        //
        //  If the path is \\server (no share), we can't handle it.
        //

        if (EffectiveRoot == NULL) {
            SetLastError(ERROR_BAD_PATHNAME);
            return FALSE;
        }

        EffectiveRoot = _tcschr(&EffectiveRoot[1], '\\');

        //
        //  If the path is \\?\UNC\server\share (exactly, no trailing
        //  component), it's as full as it'll ever get.  We should
        //  just return it here.
        //

        if (EffectiveRoot == NULL) {
            *lpBuffer = AbsoluteName;
            if (lpFilePart != NULL) {
                *lpFilePart = NULL;
            }
            return TRUE;
        }

        PreviousWasSeperator = FALSE;

    } else if (PrefixPresent) {

        EffectiveRoot = &AbsoluteName[4];
        PreviousWasSeperator = TRUE;

    } else {

        EffectiveRoot = &AbsoluteName[7];
        PreviousWasSeperator = TRUE;
    }

    //
    //  Now process the path to remove duplicate slashes, remove .
    //  components, and process .. components.
    //

    CurrentWriteChar = EffectiveRoot;

    for (CurrentReadChar = EffectiveRoot; *CurrentReadChar != '\0'; CurrentReadChar++) {

        //
        //  Strip duplicate backslashes
        //

        if (PreviousWasSeperator &&
            CurrentReadChar[0] == '\\') {

            continue;
        }

        //
        //  If the component is \.\, strip it
        //

        if (PreviousWasSeperator && 
            CurrentReadChar[0] == '.' &&
            (CurrentReadChar[1] == '\\' || CurrentReadChar[1] == '\0')) {

            if (CurrentWriteChar > EffectiveRoot) {
                CurrentWriteChar--;
                PreviousWasSeperator = FALSE;
            }

            continue;
        }

        //
        //  If the component is \..\, back up, but don't continue beyond
        //  EffectiveRoot
        //

        if (PreviousWasSeperator && 
            CurrentReadChar[0] == '.' &&
            CurrentReadChar[1] == '.' &&
            (CurrentReadChar[2] == '\\' || CurrentReadChar[2] == '\0')) {

            PreviousWasSeperator = FALSE;

            //
            //  Walk back one component or until the root
            //

            CurrentWriteChar--;
            while (CurrentWriteChar > EffectiveRoot) {

                CurrentWriteChar--;

                if (*CurrentWriteChar == '\\') {
                    break;
                }
            }

            //
            //  If we were already on effective root when entering
            //  this block, we backed up one char too many already
            //

            if (CurrentWriteChar < EffectiveRoot) {
                CurrentWriteChar = EffectiveRoot;
            }

            //
            //  If we get to the root, check if the previous
            //  char was a seperator.  Messy because UNC and
            //  drive letters resolve this differently
            //

            if (CurrentWriteChar == EffectiveRoot) {
                CurrentWriteChar--;
                if (*CurrentWriteChar == '\\') {
                    PreviousWasSeperator = TRUE;
                }
                CurrentWriteChar++;
            }

            CurrentReadChar++;
            continue;
        }

        //
        //  Note if this is a seperator or not.  If it's the final char and
        //  a seperator, drop it
        //

        if (*CurrentReadChar == '\\') {
            PreviousWasSeperator = TRUE;
        } else {
            PreviousWasSeperator = FALSE;
        }
 
        *CurrentWriteChar = *CurrentReadChar;
        CurrentWriteChar++;
    }

    //
    //  One more to copy the NULL
    //

    *CurrentWriteChar = *CurrentReadChar;

    //
    //  Walk back to find the file part
    //

    while (CurrentWriteChar >= EffectiveRoot) {

        CurrentWriteChar--;

        if (*CurrentWriteChar == '\\') {
            CurrentWriteChar++;
            break;
        }
    }

    if (CurrentWriteChar < EffectiveRoot || *CurrentWriteChar == '\0') {
        CurrentWriteChar = NULL;
    }

    //
    //  Return a pointer to the allocation and file part.
    //

    *lpBuffer = AbsoluteName;
    if (lpFilePart != NULL) {
        *lpFilePart = CurrentWriteChar;
    }
    SetLastError(0);
    return TRUE;
#endif
}

DWORD
SdirGetFullPathName(
    __in LPCTSTR szFileName,
    __in DWORD nBufferLength,
    __out LPTSTR lpBuffer,
    __out_opt LPTSTR* lpFilePart
    )
{
#if !defined(SDIR_SUPPORT_LONGPATH) || !defined(UNICODE)
    return GetFullPathName(szFileName, nBufferLength, lpBuffer, lpFilePart);
#else
    LPTSTR szBuffer;
    LPTSTR szFilePart;
    DWORD dwLength;

    if (!SdirGetFullPathNameReturnAllocation(szFileName, &szBuffer, &szFilePart)) {
        return 0;
    }

    dwLength = (DWORD)_tcslen(szBuffer);

    if (nBufferLength > dwLength) {
        _stprintf_s(lpBuffer, nBufferLength, _T("%s"), szBuffer);
        if (lpFilePart != NULL) {
            if (szFilePart == NULL) {
                *lpFilePart = NULL;
            } else {
                *lpFilePart = lpBuffer + (DWORD)(szFilePart - szBuffer);
            }
        }
        HeapFree(GetProcessHeap(), 0, szBuffer);
        return dwLength;
    } else {
        HeapFree(GetProcessHeap(), 0, szBuffer);
        return dwLength + 1;
    }
#endif
}

// vim:sw=4:ts=4:et:
